Skip to content

Adopt monorepo structure#2565

Merged
fredrikekelund merged 74 commits into
trunkfrom
stu-1288-adopt-monorepo-structure
Feb 19, 2026
Merged

Adopt monorepo structure#2565
fredrikekelund merged 74 commits into
trunkfrom
stu-1288-adopt-monorepo-structure

Conversation

@fredrikekelund
Copy link
Copy Markdown
Contributor

@fredrikekelund fredrikekelund commented Feb 11, 2026

Related issues

Proposed Changes

Note

The grunt work in this PR was done using OpenAI Codex. 🚨 I know the size of the diff looks daunting, but aside from package-lock.json changes, this PR is mostly just moving files around, changing import paths and tweaking config files 🚨

Note

As noted in #2565 (comment), it's expected that the performance tests are failing.

Warning

A consequence of this change is that package-lock.json will no longer govern the version of dependencies we ship in production releases. That's because we now have a single lockfile for all npm workspaces, but we use only the package-specific package.json files to install the node_modules directories shipped in production (without a lockfile). This is probably fine, but it means we should be a bit more conservative about the semver ranges we use in package.json. For example, we cannot use ^3.0.46 for @wp-playground dependencies if the intention is to ship 3.0.46 to production. For those cases, we should save the exact version in package.json.

The CLI used to be more of an add-on to Studio, but as of v1.7.0, it's become integral to how Studio works. To reflect this, and to make the codebase easier to work with (for humans and AI agents), this PR adopts a proper monorepo structure.

So, what does "proper monorepo structure" mean here?

  • The CLI code is moved to apps/cli and the Electron app code is moved to apps/studio. They now live side by side.
  • The apps/cli code and the apps/studio code are now isolated – there are no cross imports.
  • ./common had been made into a proper package, using @studio/common as the package name. It now lives in tools/common.
    • We still use a Vite import alias for @studio/common, so it's not consumed as a true npm module. This is because we'd need to add package exports for everything we want to consume outside that package (i.e., for basically every function/type/class). This seemed tedious to me, but if it's good practice for whatever reason, we can easily revisit it later.
  • Other auxiliary tools have also been moved into tools/, like tools/eslint-plugin-studio.
  • We use npm workspaces to make it easy and convenient to install dependencies for all packages. Run npm install once in the repo root, and you're good to go.
    • This means that all packages have individual package.json files and npm reconciles and dedupes everything into a single node_modules directory and a single package-lock.json file.
  • The CLI and the app have special requirements for packaging (see pfHvTO-XV-p2#comment-820), namely that they each need separate node_modules directories that get copied into the packaged output. We handle this with install:bundle npm scripts in apps/cli/package.json and apps/studio/package.json.
    • A notable caveat of this approach is that it mutates the apps/cli/node_modules and apps/studio/node_modules directories, which potentially messes up the npm workspace-powered dependency tree. So, if you run npm run package locally, you would typically need to rerun npm ci after the script finishes to reconcile the dependency tree. It's easy for other developers to be stumped by this requirement, so I addressed it by adding a scripts/package-in-isolation.ts script. The gist is that it copies the source files from the repo to a temporary directory, installs dependencies, runs install:bundle and the package/make script, and then copies the output back to the repo.

Testing Instructions

CI should pass, and the dev build should install fine

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?

@fredrikekelund fredrikekelund requested a review from a team February 11, 2026 10:52
@fredrikekelund fredrikekelund self-assigned this Feb 11, 2026
Comment thread apps/studio/forge.config.ts Fixed
@fredrikekelund
Copy link
Copy Markdown
Contributor Author

The performance metrics CI job fails because it uses the same setup command on trunk as on this branch. There's no easy way to reconcile that, so I'd prefer to just let it fail in this PR and have it work for the next PR that runs after this one.

@fredrikekelund
Copy link
Copy Markdown
Contributor Author

@wojtekn, the dev build installs and runs fine on macOS for me.

Copy link
Copy Markdown
Contributor

@epeicher epeicher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @fredrikekelund for implementing these massive changes! I have tested different areas, and I have not found any issues. With regards to the scripts, I have tested:

Platform Test Result
Mac npm install
Mac npm start
Mac npm run make
Windows npm install
Windows npm start

I approve this as it works for me, but I'm open to feedback from others.

Comment thread apps/cli/lib/tests/api.test.ts Outdated
@@ -1,6 +1,6 @@
import fs from 'fs';
import wpcomFactory from '@studio/common/lib/wpcom-factory';
import { createMock } from 'src/lib/test-utils';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: The src alias maps to apps/studio/src, so this CLI test file imports from the Studio app. This breaks the isolation goal. createMock should be moved to tools/common or the test should use its own utility.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch 👍 This made me realize we should split the Vitest config the same way we do with the Vite config. This ensures the resolve aliases are identical between Vite and Vitest, and tests cannot import files that aren't accessible to source code files. I did this in 7ddeec8.

@epeicher
Copy link
Copy Markdown
Contributor

A consequence of this change is that package-lock.json will no longer govern the version of dependencies we ship in production releases. That's because we now have a single lockfile for all npm workspaces, but we use only the package-specific package.json files to install the node_modules directories shipped in production (without a lockfile). This is probably fine, but it means we should be a bit more conservative about the semver ranges we use in package.json. For example, we cannot use ^3.0.46 for @wp-playground dependencies if the intention is to ship 3.0.46 to production. For those cases, we should save the exact version in package.json.

For a follow-up, we could consider improving this, an alternative to investigate is pnpm deploy, which creates a standalone install of a workspace package with locked versions from the root lockfile. It's purpose-built for this exact use case. But migrating from npm to pnpm is a separate undertaking.

@epeicher
Copy link
Copy Markdown
Contributor

Also, I would like to note that when running npm run make on Windows, I'm getting the following error:

✗ Build failed in 31.12s
error during build:
[vite:build-html] The "fileName" or "name" properties of emitted chunks and assets must be strings that are neither absolute nor relative paths, received "../../../../../../../../username/AppData/Local/Temp/studio-package-NbjiPI/repo/apps/studio/index.html".
    at getRollupError (file:///C:/Users/username/AppData/Local/Temp/studio-package-NbjiPI/repo/node_modules/rollup/dist/es/shared/parseAst.js:401:41)

@wojtekn
Copy link
Copy Markdown
Contributor

wojtekn commented Feb 18, 2026

@fredrikekelund I tried again, and it still crashes.

I did the following:

  1. Pull the changes from the branch
  2. Remove old directories and dependencies (dist, node_modules, assets, bin, common, cli, e2e, src, out)
  3. Run npm install
  4. Run npm run make

When I launch Studio, it immediately crashes:

CleanShot 2026-02-18 at 09 28 52@2x

It doesn't seem as flakiness as it clearly misses library:

Library not loaded: @rpath/Electron Framework.framework/Electron Framework
Referenced from: <4C4C4492-5555-3144-A1A8-5F0D2C2A6582> /Users/USER/Sites/*/Studio.app/Contents/MacOS/Studio
Reason: tried: '/Users/cyphelf/Sites/studio/apps/studio/out/Studio-darwin-arm64/Studio.app/Contents/Frameworks/Electron Framework.framework/Electron Framework' (no such file), '/Users/cyphelf/Sites/studio/apps/studio/out/Studio-darwin-arm64/Studio.app/Contents/Frameworks/Electron Framework.framework/Electron Framework' (no such file)
(terminated at launch; ignore backtrace)

I checked it more, and it is a symlink pointing to a build-time directory, which I guess, was removed after the build:

lrwxr-xr-x   1 cyphelf  _www  209 Feb 18 08:57 Electron Framework -> /private/var/folders/6x/h7nh9c9d5fg_jxtx7bc053240000gr/T/studio-package-IfODkM/repo/apps/studio/out/Studio-darwin-arm64/Studio.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Electron Framework

Studio 1.7.4 installed from DMG has a symlink, too, but it seems to be relative, pointing to another dir from the package:

lrwxr-xr-x@  1 cyphelf  staff   35 Feb 17 12:24 Electron Framework -> Versions/Current/Electron Framework

@fredrikekelund
Copy link
Copy Markdown
Contributor Author

pnpm deploy

That's super interesting! Thanks for highlighting, @epeicher. Worth considering.

@fredrikekelund
Copy link
Copy Markdown
Contributor Author

I tried again, and it still crashes

I'm taking another look and trying to reproduce this. The package/make routine varies between local environments and CI now (we package in isolation locally only). It's possible that this is causing the error you encountered, @wojtekn

I would like to note that when running npm run make on Windows, I'm getting the following error

Looking into this, too. Again, CI passed, but I'm running npm run make locally to confirm.

@fredrikekelund
Copy link
Copy Markdown
Contributor Author

@epeicher, I couldn't reproduce the Windows error you described. npm run make works fine for me, and I can start apps\studio\out\Studio-win32-arm64\Studio.exe just fine after building.

@wojtekn, I could reproduce the error you described, and it was indeed related to the package-in-isolation.ts logic (meaning it only happens locally, and not on CI). The fix was simple. See 1895468.

@fredrikekelund fredrikekelund merged commit 560b01c into trunk Feb 19, 2026
7 of 9 checks passed
@fredrikekelund fredrikekelund deleted the stu-1288-adopt-monorepo-structure branch February 19, 2026 11:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants